package qj.tool.jna;


import java.awt.event.KeyEvent;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;

import qj.tool.jna.User32.INPUT;
import qj.tool.jna.User32.KEYBDINPUT;
import qj.ui.DesktopUI4;
import qj.util.PropertiesUtil;
import qj.util.ReflectUtil;
import qj.util.SystemUtil;
import qj.util.ThreadUtil;
import qj.util.funct.*;

public class KeyboardHandler {
	public static final int INSERT = 45;
	public static final int DELETE = 46;
	public static final int SPACE = 32;
	
	public static final int LEFT = 37;
	public static final int RIGHT = 39;
	public static final int UP = 38;
	public static final int DOWN = 40;

	public static final int ESC = KeyEvent.VK_ESCAPE;
	public static final int NUM_1 = KeyEvent.VK_NUMPAD1;

	public static final int PGUP = 33;
	public static final int PGDOWN = 34;
	
	public static final int TAB = 9;
	public static final int ENTER = 13;
	public static final int WINDOWS = 91;
	public static final int LALT = SystemUtil.isWindows() ? 164 : KeyEvent.VK_ALT;
	public static final int RALT = 165;
	public static final int LSHIFT = SystemUtil.isWindows() ? 160 : KeyEvent.VK_SHIFT;
	public static final int RSHIFT = 161;
	public static final int BACKQUOTE = 192;
	public static final int CAPSLOCK = 20;
	public static final int LCTRL = SystemUtil.isWindows() ? 162 : KeyEvent.VK_CONTROL;
	public static final int RCTRL = 163;

	public static final int PRINTSCREEN = 44;
	public static final int SCROLL_LOCK = 145;
	public static final int PAUSE = 19;

	public static final int COMMA = 188;
	public static final int HORIZONTAL_DASH = 189;
	public static final int DOT = 190;
	public static final int SLASH = 191;
	public static final int SEMICOLON = 186;
	public static final int QUOTE = 222;
	
	public static final int SQUARE_BRACKET_OPEN = 219;
	public static final int SQUARE_BRACKET_CLOSE = 221;

	public static final int BACK_SPACE = 8;
	public static final int BACK_SLASH = 220;
	public static final int EQUAL = 187;

	public static final int F1 = 112;
	public static final int F2 = 113;
	public static final int F3 = 114;
	public static final int F4 = 115;
	public static final int F5 = 116;
	public static final int F6 = 117;
	public static final int F7 = 118;
	public static final int F8 = 119;
	public static final int F9 = 120;
	public static final int F10 = 121;
	public static final int F11 = 122;
	public static final int F12 = 123;

	private static final HashSet<Integer> keysPressed = new HashSet<Integer>();
	private static List<Douce<F2<Integer, Boolean, Boolean>, F2<Integer, Boolean, Boolean>>> listeners = new ArrayList<Douce<F2<Integer, Boolean, Boolean>, F2<Integer, Boolean, Boolean>>>();

	protected static boolean inform(Boolean pressed, Integer key) {
//		System.out.println("Key: " + key + ", pressed: " + pressed);
		
		if (pressed && !keysPressed.contains(key)) {
//			System.out.println("Add Key: " + key);
			keysPressed.add(key);
		} else if (!pressed) {
//			System.out.println("Remove Key: " + key);
			keysPressed.remove(key);
		}
		
		for (Douce<F2<Integer, Boolean, Boolean>, F2<Integer, Boolean, Boolean>> entry : listeners) {
		    if (entry.get1().e(key, pressed)) {
				F2<Integer, Boolean, Boolean> listener = entry.get2();
				if (listener != null) {
					Boolean result = listener.e(key, pressed);
					if (result == null || !result) {
						return false;
					}
				}
			}
		}
		return true;
	}
	
    static {
    	if (SystemUtil.isWindows()) {
	    	F2<Integer, Boolean, Boolean> keyboardHook = new F2<Integer, Boolean, Boolean>(){public Boolean e(Integer key, Boolean pressed) {
				return inform(pressed, key);
	    	}};
	    	
	        JnaUtil.setKeyboardHook(keyboardHook);
    	} else {
    		ReflectUtil.invoke(ReflectUtil.getMethod("init", ReflectUtil.forName("qj.tool.jna.KeyboardHooks")), null);
    	}
    }
    
    public static P1<F2<Integer, Boolean, Boolean>> createMapOnceF() {
    	final AtomicReference<F2<Integer, Boolean, Boolean>> ref = new AtomicReference<F2<Integer, Boolean, Boolean>>();
    	mapAll(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean b) {
    		F2<Integer, Boolean, Boolean> currentF = ref.get();
			if (currentF==null) {
    			return true;
    		}
			if (currentF.e(a, b)) {
				ref.set(null);
				return false;
			}
			return true;
		}});
    	
    	return new P1<F2<Integer, Boolean, Boolean>>() {public void e(F2<Integer, Boolean, Boolean> obj) {
    		ref.set(obj);
		}};
    }
    
    public static void onHold(int key, final P1<F0<Boolean>> action) {
    	final boolean[] running = {false};
    	final boolean[][] interruptedRef = {null};
		mapPressed(key, new P0() {public void e() {
			if (running[0]) {
				return;
			}
			running[0] = true;
			interruptedRef[0] = new boolean[] {false};
			ThreadUtil.run(new P0() {public void e() {
				action.e(Fs.booleanRef(interruptedRef[0]));
				running[0] = false;
			}});
		}});
		mapReleased(key, new P0() {public void e() {
			if (interruptedRef[0] != null) {
				interruptedRef[0][0] = true;
				interruptedRef[0] = null;
			}
		}});
    }
    
    public static void main(String[] args) {
//		onHold(F1, new P1<F0<Boolean>>() {public void e(F0<Boolean> interrupted) {
//			while (!interrupted.e()) {
//				System.out.println("He he");
//				ThreadUtil.sleep(200);
//			}
//		}});
    	
    	listenAll(new P2<Integer, Boolean>() {
			
			@Override
			public void e(Integer a, Boolean b) {
				System.out.println(a);
			}
		});
	}
	
	public static P1<P1<Integer>> createMapOnceP1() {
		final P1<F2<Integer, Boolean, Boolean>> createMapOnceF = createMapOnceF();
		return new P1<P1<Integer>>() {public void e(final P1<Integer> p1) {
			createMapOnceF.e(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean b) {
				if (b) {
					p1.e(a);
					return true;
				}
				return false;
			}});
		}};
	}

	public static void map(F2<Integer, Boolean,Boolean> condition, F2<Integer, Boolean, Boolean> action) {
		listeners.add(0,new Douce<F2<Integer, Boolean, Boolean>, F2<Integer, Boolean, Boolean>>(condition, action));
	}
	
	public static void clear() {
		listeners.clear();
	}

	public static boolean isPressed(int key) {
		return keysPressed.contains(key);
	}
	public static boolean isCtrlPressed() {
		boolean ctrlPressed = keysPressed.contains(LCTRL) || keysPressed.contains(RCTRL);
//		System.out.println("ctrlPressed=" + ctrlPressed);
		return ctrlPressed;
	}
	public static boolean isShiftPressed() {
		boolean shiftPressed = keysPressed.contains(LSHIFT) || keysPressed.contains(RSHIFT);
//		System.out.println("shiftPressed=" + shiftPressed);
		return shiftPressed;
	}
	public static boolean isAltPressed() {
		boolean altPressed = keysPressed.contains(LALT) || keysPressed.contains(RALT);
//		System.out.println("altPressed=" + altPressed);
		return altPressed;
	}
	public static boolean isMetaPressed() {
		boolean metaPressed = keysPressed.contains(KeyEvent.VK_META);
//		System.out.println("metaPressed=" + metaPressed);
		return metaPressed;
	}
	

	public static void map(final int from, final int to) {
		map(from, new F2<Integer, Boolean, Boolean> () {public Boolean e(Integer key, Boolean pressed) {
			if (pressed) {
				DesktopUI4.keyPress(to);
			} else {
				DesktopUI4.keyRelease(to);
			}
			return false;
		}});
	}

	/**
	 * Note that this will be called twice with every press: key up and key down
	 * @param from
	 * @param action
	 */
	public static void map(final int from, final F2<Integer, Boolean, Boolean> action) {
		map(new F2<Integer, Boolean, Boolean> () {public Boolean e(Integer key, Boolean pressed) {
			return key == from;
		}}, action);
	}

	public static class TestCaptureAll {
		public static void main(String[] args) {
			listenAll(new P2<Integer,Boolean>() {public void e(Integer a, Boolean b) {
				System.out.println(a);
			}});
		}
	}
	
	public static void main1(String[] args) {
//		AtomicReference<P1<Integer>> ref = new AtomicReference<P1<Integer>>();
//		Fs.atomicP1(ref);
////		new P1<Integer>() {public void e(Integer key) {
////			
////		}}
////		mapAllPressed(Fs.f1(Fs.atomicP1(ref), true));
		
//		KeyboardHooks.init();
//		P1<P1<Integer>> createMapOnceP1 = KeyboardHooks.createMapOnceP1();
//		createMapOnceP1.e(new P1<Integer>() {public void e(Integer obj) {
//			System.out.println(obj);
//		}});
//		SystemUtil.readLine();
//		KeyboardHandler.mapAll(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer key, Boolean pressed) {
//			if (pressed) {
////				System.out.println(System.currentTimeMillis());
//				System.out.println(key);
//			}
//			return false;
//		}});
//		P1<qj.util.funct.F2<Integer, Boolean, Boolean>> createMapOnceF = createMapOnceF();
//		createMapOnceF.e(new F2<Integer, Boolean, Boolean>() {
//			
//			@Override
//			public Boolean e(Integer a, Boolean b) {
//				System.out.println(a);
//				return true;
//			}
//		});
	}

	public static void mapAll(final F2<Integer, Boolean, Boolean> action) {
		F2<Integer, Boolean, Boolean> condition = Fs.<Integer, Boolean, Boolean>f2(true);
		map(condition, action);
	}
	public static void listenAll(final P2<Integer, Boolean> action) {
		map(Fs.<Integer, Boolean, Boolean>f2(true), Fs.f2(action, true));
	}
	public static void listenAll(Runnable runnable) {
		listenAll(Fs.<Integer, Boolean>p2(runnable));
	}

	public static void mapExcept(final int exceptKey, final F2<Integer, Boolean, Boolean> action) {
		map(new F2<Integer, Boolean, Boolean> () {public Boolean e(Integer key, Boolean pressed) {
			return key != exceptKey;
		}}, action);
	}

	public static void disableKey(final int disabledKey) {
		map(new F2<Integer, Boolean,Boolean>() {public Boolean e(Integer key, Boolean pressed) {
			return key == disabledKey;
		}}, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer key, Boolean pressed) {
			return false;
		}});
	}

	public static void listenPressed(final int btn, Runnable run) {
		map(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean pressed) {
			return (a == btn) && pressed;
		}}, Fs.<Integer, Boolean, Boolean>f2(run, true)
		);
	}

	public static void mapAllPressed(final F1<Integer,Boolean> action) {
		map(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean pressed) {
			return pressed;
		}}, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean pressed) {
			return action.e(a);
		}}
		);
	}
	public static void listen(final int btn, final P1<Boolean> p1) {
		map(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean pressed) {
			return a == btn;
		}}, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer key, Boolean pressed) {
			p1.e(pressed);
			return true;
		}}
		);
	}
	public static void map(final int btn, final P1<Boolean> p1) {
		map(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean pressed) {
//			System.out.println(a);/s
			return a == btn;
		}}, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer key, Boolean pressed) {
			p1.e(pressed);
			return false;
		}}
		);
	}
	public static void listenPressed(final int btn, P0 run) {
		map(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean pressed) {
			return (a == btn) && pressed;
		}}, Fs.<Integer, Boolean, Boolean>f2(run, true)
		);
	}
	public static void listenRelease(final int btn, P0 run) {
		map(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean pressed) {
			return (a == btn) && !pressed;
		}}, Fs.<Integer, Boolean, Boolean>f2(run, true)
		);
	}
	public static void map(final int btn, final F1<Boolean,Boolean> run) {
		map(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean b) {
			return a == btn;
		}}, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean pressed) {
			return run.e(pressed);
		}}
		);
	}

	public static P1<Boolean> mapKeyboard(final F1<Integer, Runnable> f) {
		final boolean[] activated = new boolean[] {true};
		final HashMap<Integer, Runnable> stoppers = new HashMap<Integer, Runnable>();
		mapAll(new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer key, Boolean pressed) {
			if (!activated[0]) {
				return true;
			}
			if (pressed && isPressed(key)) {
				return !stoppers.containsKey(key);
			}
			if (pressed) {
				Runnable stopper = f.e(key);
				if (stopper!=null) {
					stoppers.put(key, stopper);
					return false;
				}
			} else {
				Runnable stopper = stoppers.remove(key);
				if (stopper != null) {
					stopper.run();
				}
			}
			return true;
		}});
		return new P1<Boolean>() { public void e(Boolean activate) {
			activated[0] = activate;
		}};
	}

	public static void mapPressed(int key, final Runnable silent) {
		map(key, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean b) {
			if (b) {
				silent.run();
			}
			return false;
		}});
	}

	public static void mapPressed(int key, final P0 p0) {
		map(key, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean b) {
			if (b) {
				p0.e();
				return false;
			}
			return true;
		}});
	}

	public static void mapReleased(int key, final P0 p0) {
		map(key, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean b) {
			if (!b) {
				p0.e();
				return false;
			}
			return true;
		}});
	}

	public static void mapPressed(int key, final F0<Boolean> f) {
		final Boolean[] pressed = {null};
		map(key, new F2<Integer, Boolean, Boolean>() {public Boolean e(Integer a, Boolean b) {
			if (b) {
				Boolean p = f.e();
				if (p != null && p) {
					pressed[0] = true;
				}
				return p;
			}
			
			if (pressed[0] != null && pressed[0]) {
				pressed[0] = false;
				return true;
			}
			return false;
		}});
	}
	
	

    public static void maina(String[] args) {
    	User32 user32 = User32.INSTANCE;

    	INPUT in = new INPUT();
    	in.type = 1;
    	in.anon.ki.wVk = 0x5B; // Keycode f�r LWIN
    	in.anon.ki.dwFlags = 0x00; // Keydown

    	in.anon.setType(KEYBDINPUT.class); // this will select the "ki" field

    	INPUT[] input = (INPUT[])in.toArray(1);
    	int out = user32.SendInput(input.length, input, 28);
	}

    public static void press(int key) {
    	sendKey(key, 0);
    }
    public static void release(int key) {
    	sendKey(key, 2);
    }

	private static void sendKey(int key, int flag) {
		User32 user32 = User32.INSTANCE;

    	INPUT in = new INPUT();
    	in.type = 1;
    	in.anon.ki.wVk = (short) key;
//    	in.anon.ki.wVk = 0;
//    	in.anon.ki.wScan = (short) key;
		in.anon.ki.dwFlags = flag; // Keydown

    	in.anon.setType(KEYBDINPUT.class); // this will select the "ki" field

    	INPUT[] input = (INPUT[])in.toArray(1);
//    	int out = 
		user32.SendInput(input.length, input, 28);
	}
    
	static HashMap<Integer, String> names = new HashMap<Integer, String>();
	public static P0 clearF = new P0() {public void e() {
		clear();
	}};
	static {
		Properties props = PropertiesUtil.load(KeyboardHandler.class.getResourceAsStream("names.properties"));
		for (Entry<Object, Object> entry : props.entrySet()) {
			names.put(Integer.parseInt(String.valueOf(entry.getKey())), 
					String.valueOf(entry.getValue()));
		}
	}
	
	public static String getName(int btnCode) {
		String name = names.get(btnCode);
		if (name != null) {
			return name;
		}
		return "" + btnCode;
	}

	
	public static void block(int key) {
		map(key, new F1<Boolean, Boolean>() {public Boolean e(Boolean obj) {
			return false;
		}});
	}
}
